﻿Imports System.IO
Imports System.Text

Public Class Password
    Dim pPassword As String
    Dim pKeyFile As New KeyFileCollection

    Public Property Password As String
        Set(value As String)
            pPassword = value
        End Set
        Get
            Return pPassword
        End Get
    End Property

    Public Property KeyFile As KeyFileCollection
        Set(value As KeyFileCollection)
            pKeyFile = value
        End Set
        Get
            Return pKeyFile
        End Get
    End Property

    Friend Function ApplyKeyFile(ByRef Password As String) As Boolean
        Dim keyPool(KEYFILE_POOL_SIZE - 1) As Byte, Ret(MAX_PASSWORD) As Byte
        Dim fInfo As FileInfo

        'For i As Integer = 0 To KEYFILE_POOL_SIZE - 1
        '    keyPool(i) = 204
        'Next

        For Each KeyFile As KeyFile In pKeyFile
            'If it's a token
            'TODO

            If KeyFile.IsDirectory Then 'If it's a directory
                For Each sFile As String In Directory.EnumerateFiles(KeyFile.FileName, "*.*", SearchOption.TopDirectoryOnly)
                    fInfo = New FileInfo(sFile)

                    If Not fInfo.Attributes And FileAttributes.Hidden Then
                        If Not ProcessKeyFile(keyPool, KeyFile.FileName) Then Return False
                    End If
                Next
            Else 'If it's a file
                If Not ProcessKeyFile(keyPool, KeyFile.FileName) Then Return False
            End If
        Next

        'Mix the keyfile pool contents into the password
        For i As Integer = 0 To keyPool.Length - 1
            If i < pPassword.Length Then
                Ret(i) = (Asc(pPassword(i)) + keyPool(i)) Mod 256
            Else
                Ret(i) = keyPool(i)
            End If
        Next

        Password = Encoding.Default.GetString(Ret)

        keyPool = Nothing
        Ret = Nothing

        Return True
    End Function

    Private Function ProcessKeyFile(ByRef keyPool() As Byte, ByVal FileName As String) As Boolean
        Dim fsFile As FileStream, frFile As BinaryReader
        Dim src As IntPtr, ftCreationTime As Long, ftLastWriteTime As Long, ftLastAccessTime As Long, bTimeStampValid As Boolean = False
        Dim bytesRead As Integer, totalRead As Integer, buffer(64 * 1024) As Byte, crc As UInteger = 4294967295
        Dim writePos As Integer = 0

        src = CreateFile(FileName, FileAccess.ReadWrite, FileShare.ReadWrite, Nothing, FileMode.Open, 0, Nothing)

        If Not src = INVALID_HANDLE_VALUE Then
            If GetFileTime(src, ftCreationTime, ftLastAccessTime, ftLastWriteTime) Then bTimeStampValid = True
        End If

        fsFile = New FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
        frFile = New BinaryReader(fsFile)

        bytesRead = frFile.Read(buffer, 0, buffer.Length)
        While bytesRead > 0
            For i As Integer = 0 To bytesRead - 1
                crc = UPDC32(buffer(i), crc)

                keyPool(writePos) = (keyPool(writePos) + ((crc >> 24) Mod 256)) Mod 256
                keyPool(writePos + 1) = (keyPool(writePos + 1) + ((crc >> 16) Mod 256)) Mod 256
                keyPool(writePos + 2) = (keyPool(writePos + 2) + ((crc >> 8) Mod 256)) Mod 256
                keyPool(writePos + 3) = (keyPool(writePos + 3) + ((crc) Mod 256)) Mod 256
                writePos += 4

                If writePos >= KEYFILE_POOL_SIZE Then
                    writePos = 0
                End If

                If totalRead >= KEYFILE_MAX_READ_LEN Then GoTo close
                totalRead += 1
            Next

            bytesRead = frFile.Read(buffer, 0, buffer.Length)
        End While

close:
        If bTimeStampValid And Not IsFileOnReadOnlyFilesystem(FileName) Then
            SetFileTime(src, ftCreationTime, ftLastAccessTime, ftLastWriteTime)
        End If

        frFile.Close()
        fsFile.Close()

        CloseHandle(src)

        If totalRead = 0 Then Return False

        Return True
    End Function
End Class

Public Class KeyFile
    Dim pFileName As String
    Dim pIsDirectory As Boolean

    Public Sub New(ByVal FileName As String)
        pFileName = FileName
        pIsDirectory = Directory.Exists(FileName)
    End Sub

    Public ReadOnly Property FileName As String
        Get
            Return pFileName
        End Get
    End Property

    Public ReadOnly Property IsDirectory As Boolean
        Get
            Return pIsDirectory
        End Get
    End Property
End Class

Public Class KeyFileCollection
    Implements IList(Of KeyFile) ', IEnumerable(Of KeyFile)

    Dim KeyFiles As New List(Of KeyFile)

    Public Sub Add(item As KeyFile) Implements ICollection(Of KeyFile).Add
        KeyFiles.Add(item)
    End Sub

    Public Sub Add(item As String)
        KeyFiles.Add(New KeyFile(item))
    End Sub

    Public Sub Clear() Implements ICollection(Of KeyFile).Clear
        KeyFiles.Clear()
    End Sub

    Public Function Contains(item As KeyFile) As Boolean Implements ICollection(Of KeyFile).Contains
        Return KeyFiles.Contains(item)
    End Function

    Public Sub CopyTo(array() As KeyFile, arrayIndex As Integer) Implements ICollection(Of KeyFile).CopyTo
        KeyFiles.CopyTo(array, arrayIndex)
    End Sub

    Public ReadOnly Property Count As Integer Implements ICollection(Of KeyFile).Count
        Get
            Return KeyFiles.Count
        End Get
    End Property

    Public ReadOnly Property IsReadOnly As Boolean Implements ICollection(Of KeyFile).IsReadOnly
        Get
            Return False
        End Get
    End Property

    Public Function Remove(item As KeyFile) As Boolean Implements ICollection(Of KeyFile).Remove
        Return KeyFiles.Remove(item)
    End Function

    Public Function GetEnumerator() As IEnumerator(Of KeyFile) Implements IEnumerable(Of KeyFile).GetEnumerator
        Return New KeyFileEnumerator(KeyFiles)
    End Function

    Public Function IndexOf(item As KeyFile) As Integer Implements IList(Of KeyFile).IndexOf
        Return KeyFiles.IndexOf(item)
    End Function

    Public Sub Insert(index As Integer, item As KeyFile) Implements IList(Of KeyFile).Insert
        KeyFiles.Insert(index, item)
    End Sub

    Default Public Property Item(index As Integer) As KeyFile Implements IList(Of KeyFile).Item
        Get
            Return KeyFiles(index)
        End Get
        Set(value As KeyFile)
            KeyFiles(index) = value
        End Set
    End Property

    Public Sub RemoveAt(index As Integer) Implements IList(Of KeyFile).RemoveAt
        KeyFiles.RemoveAt(index)
    End Sub

    Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
        Return New KeyFileEnumerator(KeyFiles)
    End Function
End Class

Public Class KeyFileEnumerator
    Implements IEnumerator(Of KeyFile)

    Dim KeyFiles As List(Of KeyFile)
    Dim position As Integer = -1

    Public Sub New(ByVal KeyFiles As List(Of KeyFile))
        Me.KeyFiles = KeyFiles
    End Sub

    Public ReadOnly Property Current As KeyFile Implements IEnumerator(Of KeyFile).Current
        Get
            Try
                Return KeyFiles(position)
            Catch ex As IndexOutOfRangeException
                Throw New InvalidOperationException()
            End Try
        End Get
    End Property

    Public ReadOnly Property Current1 As Object Implements IEnumerator.Current
        Get
            Try
                Return KeyFiles(position)
            Catch ex As IndexOutOfRangeException
                Throw New InvalidOperationException()
            End Try
        End Get
    End Property

    Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
        position += 1
        Return position < KeyFiles.Count
    End Function

    Public Sub Reset() Implements IEnumerator.Reset
        position = -1
    End Sub

#Region "IDisposable Support"
    Private disposedValue As Boolean ' Per rilevare chiamate ridondanti

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: eliminare stato gestito (oggetti gestiti).
            End If

            ' TODO: liberare risorse non gestite (oggetti non gestiti) ed eseguire l'override del seguente Finalize().
            ' TODO: impostare campi di grandi dimensioni su null.
        End If
        Me.disposedValue = True
    End Sub

    ' TODO: eseguire l'override di Finalize() solo se Dispose(ByVal disposing As Boolean) dispone del codice per liberare risorse non gestite.
    'Protected Overrides Sub Finalize()
    '    ' Non modificare questo codice. Inserire il codice di pulizia in Dispose(ByVal disposing As Boolean).
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' Questo codice è aggiunto da Visual Basic per implementare in modo corretto il modello Disposable.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Non modificare questo codice. Inserire il codice di pulizia in Dispose(disposing As Boolean).
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class